りおんクロニクル


SQLite × WPF DataGrid(MVVM対応)|完全バインディング構成で一覧・編集・保存【2026年版】

Home【2026年版】C# / .NET入門と実践ガイド|基礎・業務アプリ開発・SQLite連携まで体系的に解説

WPFで業務アプリを作るなら、SQLite × DataGrid × MVVM はほぼ定番構成です。 この記事では、SQLiteのデータをMVVMパターンでDataGridにバインドし、 一覧表示・編集・保存・削除までをコードビハインド最小で実装する方法をまとめます。

この記事でわかること
・SQLiteのデータをMVVMでDataGridに表示する方法
・ObservableCollection+INotifyPropertyChangedの構成
・保存・削除をコマンドで実装するパターン
・新規追加行のINSERT処理
・業務アプリ向けベストプラクティス

1. 前提:テーブルとライブラリ

CREATE TABLE Users (
    Id   INTEGER PRIMARY KEY AUTOINCREMENT,
    Name TEXT    NOT NULL,
    Age  INTEGER
);

2. モデル+INotifyPropertyChanged

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class User : INotifyPropertyChanged
{
    private int _id;
    private string _name;
    private int? _age;

    public int Id
    {
        get => _id;
        set { _id = value; OnPropertyChanged(); }
    }

    public string Name
    {
        get => _name;
        set { _name = value; OnPropertyChanged(); }
    }

    public int? Age
    {
        get => _age;
        set { _age = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

3. RelayCommand(汎用コマンド)

using System;
using System.Windows.Input;

public class RelayCommand : ICommand
{
    private readonly Action<object?> _execute;
    private readonly Func<object?, bool> _canExecute;

    public RelayCommand(Action<object?> execute, Func<object?, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute ?? (_ => true);
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public bool CanExecute(object parameter) => _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

4. ViewModel:一覧・保存・削除・再読込

using Microsoft.Data.Sqlite;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;

public class UsersViewModel : INotifyPropertyChanged
{
    private readonly string _cs = "Data Source=sample.db";

    public ObservableCollection<User> Users { get; } = new();

    private User _selectedUser;
    public User SelectedUser
    {
        get => _selectedUser;
        set { _selectedUser = value; OnPropertyChanged(); }
    }

    public ICommand ReloadCommand { get; }
    public ICommand SaveCommand { get; }
    public ICommand DeleteCommand { get; }
    public ICommand AddNewCommand { get; }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    public UsersViewModel()
    {
        ReloadCommand = new RelayCommand(_ => Load());
        SaveCommand   = new RelayCommand(_ => SaveAll(), _ => Users.Any());
        DeleteCommand = new RelayCommand(u => Delete(u as User), u => u is User);
        AddNewCommand = new RelayCommand(_ => AddNew());

        Load();
    }

    public void Load()
    {
        Users.Clear();

        using var con = new SqliteConnection(_cs);
        con.Open();

        using var cmd = new SqliteCommand("SELECT Id, Name, Age FROM Users ORDER BY Id", con);
        using var reader = cmd.ExecuteReader();

        while (reader.Read())
        {
            Users.Add(new User
            {
                Id   = reader.GetInt32(0),
                Name = reader.GetString(1),
                Age  = reader.IsDBNull(2) ? (int?)null : reader.GetInt32(2)
            });
        }
    }

    private void AddNew()
    {
        var u = new User { Id = 0, Name = "新規ユーザー", Age = null };
        Users.Add(u);
        SelectedUser = u;
    }

    public void SaveAll()
    {
        using var con = new SqliteConnection(_cs);
        con.Open();

        using var tran = con.BeginTransaction();
        try
        {
            foreach (var u in Users)
            {
                if (u.Id == 0)
                {
                    var insert = "INSERT INTO Users (Name, Age) VALUES (@name, @age); " +
                                 "SELECT last_insert_rowid();";
                    using var cmd = new SqliteCommand(insert, con, tran);
                    cmd.Parameters.AddWithValue("@name", u.Name);
                    cmd.Parameters.AddWithValue("@age", (object?)u.Age ?? DBNull.Value);

                    var newId = (long)cmd.ExecuteScalar();
                    u.Id = (int)newId;
                }
                else
                {
                    var update = "UPDATE Users SET Name = @name, Age = @age WHERE Id = @id";
                    using var cmd = new SqliteCommand(update, con, tran);
                    cmd.Parameters.AddWithValue("@name", u.Name);
                    cmd.Parameters.AddWithValue("@age", (object?)u.Age ?? DBNull.Value);
                    cmd.Parameters.AddWithValue("@id", u.Id);
                    cmd.ExecuteNonQuery();
                }
            }

            tran.Commit();
        }
        catch
        {
            tran.Rollback();
            throw;
        }
    }

    private void Delete(User user)
    {
        if (user == null) return;

        using var con = new SqliteConnection(_cs);
        con.Open();

        if (user.Id != 0)
        {
            var sql = "DELETE FROM Users WHERE Id = @id";
            using var cmd = new SqliteCommand(sql, con);
            cmd.Parameters.AddWithValue("@id", user.Id);
            cmd.ExecuteNonQuery();
        }

        Users.Remove(user);
    }
}

5. View(XAML):DataGrid+ボタンをすべてバインド

<Window x:Class="SampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SampleApp"
        Title="SQLite × WPF DataGrid MVVM" Height="450" Width="800">

    <Window.DataContext>
        <local:UsersViewModel />
    </Window.DataContext>

    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="10" HorizontalAlignment="Right">
            <Button Content="再読込" Margin="0,0,8,0"
                    Command="{Binding ReloadCommand}" />
            <Button Content="新規追加" Margin="0,0,8,0"
                    Command="{Binding AddNewCommand}" />
            <Button Content="保存" Margin="0,0,8,0"
                    Command="{Binding SaveCommand}" />
        </StackPanel>

        <DataGrid ItemsSource="{Binding Users}"
                  SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
                  AutoGenerateColumns="False"
                  Margin="10"
                  IsReadOnly="False"
                  CanUserAddRows="False">

            <DataGrid.Columns>
                <DataGridTextColumn Header="Id"
                                    Binding="{Binding Id}"
                                    IsReadOnly="True" Width="60" />
                <DataGridTextColumn Header="名前"
                                    Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
                                    Width="*" />
                <DataGridTextColumn Header="年齢"
                                    Binding="{Binding Age, UpdateSourceTrigger=PropertyChanged}"
                                    Width="80" />

                <DataGridTemplateColumn Header="削除" Width="70">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Content="削除"
                                    Padding="4,2"
                                    Command="{Binding DataContext.DeleteCommand,
                                                      RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                    CommandParameter="{Binding}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>
</Window>

コードビハインドは InitializeComponent() だけでOKです。

6. 業務アプリでのベストプラクティス

まとめ:SQLite × WPF DataGrid × MVVM は“長く保守できる”鉄板構成

「とりあえず動く」から一歩進んで、 「長く保守できるMVVM構成」にしたいとき、 SQLite × WPF DataGrid × MVVM は非常に相性の良い組み合わせです。 この記事の構成をベースに、プロジェクトに合わせて拡張してみてください。

前のページ  次のページ